<?php

namespace app\utils;

class RedisUtil
{
    private        $Key;
    protected      $Redis;
    private static $instances;
    protected      $options   = [
        'host'       => '127.0.0.1',
        'port'       => 6379,
        'password'   => '',
        'select'     => 0,
        'timeout'    => 0,//
        'expire'     => 0,
        'persistent' => false,
        'prefix'     => '',
    ];

    public function __construct()
    {
        // 判断是否有扩展(若是你的apache没reids扩展就会抛出这个异常)
        if (!extension_loaded('redis')) {
            throw new \BadFunctionCallException('not support: redis');
        }
        $this->options = config('redis');
        $this->Redis   = new \Redis();

        // 判断是否长链接
        if ($this->options['persistent']) {
            $this->Redis->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']);
        } else {
            $this->Redis->connect($this->options['host'], $this->options['port'], $this->options['timeout']);
        }

        if (!$this->Redis) {
            throw new \BadFunctionCallException('connection fail: redis');
        }
        if ($this->options['password'] != '') {
            $this->Redis->auth($this->options['password']);
        }

        if (0 != $this->options['select']) {
            $this->Redis->select($this->options['select']);
        }
    }

    /**
     * @param string $key
     * @param string ...$dynamic key动态值拼接
     * @return RedisUtil|mixed
     * @throws \Exception
     */
    public static function getInstance(string $key, string ...$dynamic)
    {
        $dynamics = '';
        foreach ($dynamic as $k => $v) {
            if ($k == 0) {
                $dynamics .= $v;
            } else {
                $dynamics .= '_' . $v;
            }
        }
        if (!isset(self::$instances)) {
            self::$instances = new self();
        }
        self::$instances->setKey($key . $dynamics);
        return self::$instances;
    }

    // 设置key
    public function setKey($key)
    {
        $this->Key = $this->options['prefix'] . $key;
    }

    /**
     * 生成唯一标识
     * @param int $length 生成长度
     * @return string
     */
    public function createNo(int $length = 5): string
    {
        $redis = $this->Redis;

        $key = $this->Key;

        //指定键值新增+1 并获取
        $reqNo = $redis->incr($key);

        //值长度已达到5位，且首位已为9，初始化
        if (strlen($reqNo) >= $length && substr($reqNo, 0, 1) == 9) {
            $redis->set($key, 1);
            $reqNo = 1;
        }

        if ($reqNo == 1) {
            //设置月末到期
            $expire = strtotime(date('Y-m', strtotime("+1 month")) . '-01 00:00:00') - time();
            $redis->expire($key, $expire);
        }

        return sprintf("%0{$length}d", $reqNo);
    }

    /**
     * 限制尝试次数 （设定时间内）
     * @param int $second 秒
     * @param int $Times 次数
     * @return false|int
     */
    public function tryTimes(int $second, int $Times = 5)
    {
        $redis = $this->Redis;

        $key = $this->Key;

        //指定键值新增+1 并获取
        $count = $redis->incr($key);
        if ($count > $Times) {
            return false;
        }

        //设置过期时间
        if ($count == 1) {
            $redis->expire($key, $second);
        }

        return (int)$count;
    }

    /**
     * 加锁
     * @param int $timeout 锁的过期时间
     * @return false|string
     */
    public function Lock(int $timeout = 10)
    {
        $redis    = $this->Redis;
        $lockName = $this->Key;//锁的名字

        #获取唯一标识符
        $identifier = uniqid();

        #锁的过期时间
        $end = time() + $timeout;

        #循环获取锁
        while (time() < $end) {
            #查看$lockName是否被上锁,为$lockName设置过期时间，防止死锁
            if ($redis->set($lockName, $identifier, ['ex' => $timeout, 'nx'])) {
                return $identifier;
            }

            #停止0.001ms
            usleep(0.001);
        }
        return false;
    }

    /**
     * 释放锁
     * @param string $identifier 锁的唯一值
     * @return bool
     */
    public function releaseLock(string $identifier): bool
    {
        $redis    = $this->Redis;
        $lockName = $this->Key;//锁的名字

        // 判断是锁有没有被其他客户端修改
        if ($redis->get($lockName) != $identifier) {
            #其他客户端修改了锁，不能删除别人的锁
            return false;
        }

        #释放锁
        $redis->del($lockName);
        return true;
    }

    public function setex($value, int $second)
    {
        $redis = $this->Redis;

        $key = $this->Key;

        return $redis->setex($key, $second, $value);
    }

    public function set($value, $options = null)
    {
        $redis = $this->Redis;

        $key = $this->Key;

        return $redis->set($key, $value, $options);
    }

    public function get()
    {
        $redis = $this->Redis;
        $key   = $this->Key;

        return $redis->get($key);
    }

    public function del()
    {
        $redis = $this->Redis;
        $key   = $this->Key;

        return $redis->del($key);
    }

    //加入list
    public function lPush($value)
    {
        $redis = $this->Redis;

        $key = $this->Key;

        return $redis->lPush($key, $value);
    }

    //删除右边
    public function rPop()
    {
        $redis = $this->Redis;

        $key = $this->Key;

        return $redis->rPop($key);
    }

    public function rPush($value)
    {
        $redis = $this->Redis;

        $key = $this->Key;

        return $redis->rPush($key, $value);
    }

    public function lPopLine($num)
    {
        $redis = $this->Redis;

        $key = $this->Key;

        $pipe = $redis->multi(\Redis::PIPELINE);
        $pipe->lRange($key, 0, $num - 1);
        $pipe->lTrim($key, $num, -1);
        return $pipe->exec();
    }


    //list长度
    public function lLen()
    {
        $redis = $this->Redis;

        $key = $this->Key;

        return $redis->llen($key);
    }

    public function zadd($score, $member)
    {
        $redis = $this->Redis;

        $key = $this->Key;

        return $redis->zadd($key, $score, $member);
    }

    public function zcard()
    {
        $redis = $this->Redis;

        $key = $this->Key;

        return $redis->zcard($key);
    }

    public function zrem($member)
    {
        $redis = $this->Redis;

        $key = $this->Key;

        return $redis->zrem($key, $member);
    }

    // 递增返回
    public function zRange($start_score = 0, $end_score = -1, $options = null)
    {
        $redis = $this->Redis;

        $key = $this->Key;

        return $redis->zrange($key, $start_score, $end_score,$options);
    }

    // 递减返回
    public function zRevRange($start_score = 0, $end_score = -1, $options = null)
    {
        $redis = $this->Redis;

        $key = $this->Key;

        return $redis->zRevRange($key, $start_score, $end_score,$options);
    }

    public function zRevRank($start, $end, $scores = null)
    {
        $redis = $this->Redis;

        $key = $this->Key;

        return $redis->zRevRange($key, $start, $end,$scores);
    }

    public function zRank($member)
    {
        $redis = $this->Redis;

        $key = $this->Key;

        return $redis->zRank($key, $member);
    }

    public function zUnionStore($output, $zSetKeys, ?array $weights = null, $aggregateFunction = null)
    {
        $redis = $this->Redis;
        return $redis->zUnionStore($output, $zSetKeys, $weights, $aggregateFunction);
    }

    public function hSet($field, $value)
    {
        $redis = $this->Redis;
        $key   = $this->Key;
        return $redis->hset($key, $field, $value);
    }

    public function hKeys()
    {
        $redis = $this->Redis;
        $key   = $this->Key;
        return $redis->hkeys($key);
    }

    public function hGet($field)
    {
        $redis = $this->Redis;
        $key   = $this->Key;
        return $redis->hget($key, $field);
    }

    public function hDel($field)
    {
        $redis = $this->Redis;
        $key   = $this->Key;
        return $redis->hdel($key, $field);
    }

    public function hGetAll()
    {
        $redis = $this->Redis;
        $key   = $this->Key;
        return $redis->hGetAll($key);
    }
}